iT邦幫忙

2022 iThome 鐵人賽

DAY 25
0
自我挑戰組

Unity 基本功能實作與日常紀錄系列 第 25

Day25: Save and Load System in Unity

  • 分享至 

  • xImage
  •  

我們在實驗的時候,會希望再登入的時候仍保有之前User 設定的參數與數值。或是可以記錄當前的最高分等等。所以今天我將會使用 PlayerPrefs 去儲存該先前的紀錄並在剛開始執行的時候顯示之前的最高分,以及使用 Binary Formatter 的讀取與寫入來幫助我們安全的儲存資料,或是讀取先前儲存的資料等。今天實作的量比較多,我們趕緊開始吧!

建置場景

  1. 首先建立一個基本的場景,如同以下:

  2. 新增一個 Empty Component 後,稱作 Dice,並且新增一個腳本 RollDice.cs

  3. 後續去新增一個 Script 。也叫做 Dice 接著開始撰寫該文本的內容。 這邊我們希望按下我們ROLL DICE button 後會隨機產生一個1~7的數值,並顯示在UI上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RollDice : MonoBehaviour
{
    public Text scoreText;

    // Roll Dice button onClick 
    public void rollDice() 
    {
        int randI = Random.Range(1, 8);
        scoreText.text = randI.ToString();
    }
    
}
  1. 回到 Unity 將其所有相關的參數設定。第一步將該 Score text 拉到我們的環境中。

  2. Button 需要去讓該文本中的 function 連結,所以就點選 Roll Dice button。 On Click 點選加號。

將 Dice 拉進到我們 function 中。

後面就去尋找我的定義的 function 的名稱。 必須要將該function 設定 public

儲存每一次結束時當時的分數

  1. 我們每一次執行程式時候都會預設一個起始分數,該分數是我們決定的,但我們可以使用 PlayerPrefs.GetInt 來將我們的資料先存取並預設為 0。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RollDice : MonoBehaviour
{
    public Text scoreText;
    public Text highScore;
    private void Start() 
    {
        highScore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
    }

    // Roll Dice button onClick 
    public void rollDice() 
    {
        int randI = Random.Range(1, 8);
        scoreText.text = randI.ToString();

        PlayerPrefs.SetInt("HighScore", randI);
    }

    
}
  • 首先新增了該 highScore 的text,接下來我們要設定該一開始 Start 的時候字串應該可以顯示甚麼,也就是說透過下面這行可以初始 highScore 的數值。注意到這邊也是讓每一次開始的時候可以將上次結束時候的資料記錄起來重啟後會回到上次的結果。
highScore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
  • 後面當我們要Random randI 並指定到PlayerPrefs,並且 SetInt 後之後在我們執行的時候就會產生上次出現的數值。
PlayerPrefs.SetInt("HighScore", number);
  • 回到Unity 後去將該 HightScore text 拉到我們的環境中。
  1. 我們執行看看就會看出哪裡有差 。首先我們 Random 到 6。接下來我們關閉執行。

接下來我們執行我們的程式,就會看到我們之前的數值 6 出現在該 HighScore 下面!

  1. 要處理我們每一次會記錄最高的分數,這邊就撰寫說當我們比我們上一次結束時分數還高的時候就會替換當前的結果。我們現在撰寫的是僅會記錄之前剛結束後的結果,但不會隨著數值超過而改變最大數值。因此這邊要加上當超過了當前最高分數後直接替換掉最高分數。
// Roll Dice button onClick 
    public void rollDice() 
    {
        int randI = Random.Range(1, 8);
        scoreText.text = randI.ToString();


        // check larger than last High score
        if(randI > PlayerPrefs.GetInt("HighScore", 0))
        {
            PlayerPrefs.SetInt("HighScore", randI);
            highScore.text = randI.ToString();                      // cover the last high score!
        }

    }
  1. 撰寫重置的方法 Reset( ) 這樣就可以讓重啟時候回到 0 開始。 這邊就需要將所有剛剛 PlayerPrefs 所記錄的結果全部盡數刪除。接下來將我們 Text 設定為 0。
// reset the score 
    public void resetScore() 
    {
        PlayerPrefs.DeleteAll();
        highScore.text = "0";
    }
  1. 回到 Unity 將其 Reset button 做設定。

  2. 後面就可以實際 Demo了。 首先我們按下 Reset 重製一下我們的紀錄。

  • 開始點選 ROLL DICE 按鈕隨機產生數值。首先出現 3 我們就顯示 High Score 為 3

  • 接下來點選後出現 6 會覆蓋掉剛剛最高分數的 3。

  • 如果 Random 比之前還小的數值就不會變換當前最高的分數。

  • 關閉執行後我們可以看到原本的數值變為 0,在執行後就會看到上一次的High Score 結果又回來了。

SAVE and LOAD System in Unity

  1. 以下是我們今天要實作的概念,我們知道說 Binary File 的可讀性低,故安全性高。

  2. 新增一個空白物件 PlayerManagement ,之後去新增一個繳本,PlayerDataManage.cs。

  3. 之後我們撰寫幾個控制去讓按鈕可以實現數值的加與減,注意到該一開始的血量為 40,並且要知道當數值已經為 0 的時候不可以再減少下去。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerDataManage : MonoBehaviour
{
    public Text setHealth;
    public Text setLevel;

    public int showHealth = 40;
    public int showLevel = 0;


    public void addLevel()
    {
        showLevel++;
        setLevel.text = showLevel.ToString(); 
    }

    public void minusLevel()
    {
        if(showLevel > 0)
        {
            showLevel--;
            setLevel.text = showLevel.ToString();
        }else{
            setLevel.text = "0";
        }
    }

    public void addHealth()
    {
        showHealth++;
        setHealth.text = showHealth.ToString();
    }
    
    public void minusHealth() 
    {
        if(showHealth > 0)
        {
            showHealth--;
            setHealth.text = showHealth.ToString();
        }else{
            setHealth.text = "0";
        }
    }
    
}
  1. 回到 Unity 中將需要的物件拉近到我們環境中。
  • 指定事件到我們的環境中

  • 這邊要注意到的地方是說 function 主要是有傳入的數值,所以說可以進行設定數值 1。也就是點選後要不然是加一或是減一。

  • 回到 Unity 執行就會看到隨著點選該上或下會改變Health與 Level 顯示的結果。

撰寫 Save 與 Load

  1. 首先新增一個 SaveLoadData.cs Scripts。 並且無須繼承任何 MonoBehavior 。接下來撰寫該文本的內容。主要目的是要去撰寫一個建構子並且取得該 PlayerDataManage 的變數資料。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[System.Serializable]
public class SaveLoadData 
{
    public int level;
    public int health;
    public float[] position;


    public SaveLoadData(PlayerDataManage player)        // use the constructor
    {
        level = player.showLevel;
        health = player.showHealth;
        position = new float[3];        // position is x, y, z

        position[0] = player.getObjPosition.position.x;
        position[1] = player.getObjPosition.position.y;
        position[2] = player.getObjPosition.position.z;
    }
  
}
  1. 接下來回到 Unity 中新增一個新的腳本 SaveLoadSystem。 一樣無須繼承 MonoBehaviour 後並且將該 SaveSystem 的文本設定為 Static 讓我們可以直接使用該內部的方法,請注意這邊就是要使用該 SaveLoadData 的建構子內物件來取得該PlayerDataManage 中的變數。
  • 接下來引入
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
  1. 開始撰寫 Save, Load 腳本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class SaveLoadSystem
{
    public static void SavePlayer(PlayerDataManage player) 
    {
        BinaryFormatter formatter = new BinaryFormatter();
        string path = Application.persistentDataPath + "/player.fun";
        FileStream stream = new FileStream(path, FileMode.Create);
        SaveLoadData data = new SaveLoadData(player);
        formatter.Serialize(stream, data);

        stream.Close();
    }


    public static SaveLoadData LoadPlayer() 
    {
        string path = Application.persistentDataPath + "/player.fun";
        if(File.Exists(path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream stream = new FileStream(path, FileMode.Open);
            SaveLoadData data = formatter.Deserialize(stream) as SaveLoadData;
            stream.Close();
            return data;
        }else{
            Debug.LogWarning("Not found the file in " + path);
            return null;
        }
    }
    
}
  1. 注意要記得新增Save 與 Load 按鈕。

  2. 回到 PlayerManagement 中去新增兩個 Save 與 Load 按鈕的 function。並且新增一個 ObjPosition 物件讓我們可以儲存該位置。

// save the data
    public void SavePlayer() 
    {
        SaveLoadSystem.SavePlayer(this);
    }

    public void LoadPlayer() 
    {
        SaveLoadData data = SaveLoadSystem.LoadPlayer();
        showLevel = data.level;
        showHealth = data.health;
        setLevel.text = showLevel.ToString();
        setHealth.text = showHealth.ToString();

    }
  1. 完整的 PlayerDataManage.cs 程式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerDataManage : MonoBehaviour
{
    public Text setHealth;
    public Text setLevel;

    public int showHealth = 40;
    public int showLevel = 0;
    public Transform getObjPosition;


    public void addLevel()
    {
        showLevel++;
        setLevel.text = showLevel.ToString(); 
    }

    public void minusLevel()
    {
        if(showLevel > 0)
        {
            showLevel--;
            setLevel.text = showLevel.ToString();
        }else{
            setLevel.text = "0";
        }
    }

    public void addHealth()
    {
        showHealth++;
        setHealth.text = showHealth.ToString();
    }
    
    public void minusHealth() 
    {
        if(showHealth > 0)
        {
            showHealth--;
            setHealth.text = showHealth.ToString();
        }else{
            setHealth.text = "0";
        }
    }


    // save the data
    public void SavePlayer() 
    {
        SaveLoadSystem.SavePlayer(this);
    }

    public void LoadPlayer() 
    {
        SaveLoadData data = SaveLoadSystem.LoadPlayer();
        showLevel = data.level;
        showHealth = data.health;
        setLevel.text = showLevel.ToString();
        setHealth.text = showHealth.ToString();

    }
    
}
  1. 到 Unity 中去新增該方法到 Save, Load Button 上面。

Demo 看看我們的結果

  1. 首先設定一下你想要的Heath = 46, Level = 4 數值。並且現在注意到我們右上角的物件的位置。

  2. 接下來點選 Save後關閉執行。

  3. 我們隨機調整一下該 ObjPosition 的位置。與剛剛的位置不同。

  4. 開始執行的時候你會發現沒什麼變動。點選 Load 就會看到剛剛我們 Save 的數值都會回來,且Obj 也回到右上角了 XD。非常的神奇!

結論:

  1. 今天說明如何儲存當前的最高分數,在每一次回到遊戲中仍會保有該最高的分數並顯示在 UI。
  2. 也了解如何透過 Binary Formatters 去 Save, Load 當前的數值,並透過建構子中的物件來控制另外一個類別中的資料,或是回傳該類別的物件中的變數,整體儲存資料的思路有效的管理整個資料的儲存。

上一篇
Day24: UI in Unity (Audio Mixer, Slider, Dropdown Control, Toggle)
下一篇
Day26: Write to Excel .csv file
系列文
Unity 基本功能實作與日常紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言